<?php

class Lspdo {

	private $pdo = null;
	private $connConfig = null;
	
	function __construct(){
		if (!class_exists('PDO'))
            $this->errorMsg("不支持PDO");
        //这里不能用require_once，需要探讨一下
        $dbConfig = include LS_PATH.'/dbconfig.php';
        if (!is_array($dbConfig)||!isset($dbConfig['dbHost'])||!isset($dbConfig['dbName'])||!isset($dbConfig['dbUser'])||!isset($dbConfig['dbPwd']))
            $this->errorMsg("数据库配置文件错误！");
        $this->connConfig = $dbConfig;
        if(!isset($this->connConfig['dbPort'])) $this->connConfig['dbPort'] = '3306';
        if(!isset($this->connConfig['dbCharset']))$this->connConfig['dbCharset'] = 'utf8';
        $this->connect();
	}
	
	/**
     * 初始化数据库连接
     * @param array $dbConfig 数据库配置信息
     * @return void
     */
    public function connect()
    {
        if ($this->pdo == null)
        {
            try
            {
                $dsn = "mysql:host={$this->connConfig['dbHost']};port={$this->connConfig['dbPort']};dbname={$this->connConfig['dbName']}";
                $this->pdo = new PDO($dsn, $this->connConfig['dbUser'], $this->connConfig['dbPwd'],array(PDO::ATTR_PERSISTENT => false));
                //$this->pdo->setAttribute(PDO::ATTR_TIMEOUT,1);
            }
            catch (PDOException $e)
            {
                $this->errorMsg('数据库连接失败: ' . $e->getMessage());
            }
   			if($this->getVersion() < 5) { $this->closeConnect();$this->errorMsg('数据库版本必须大于５.０'); } 
            $this->pdo->exec("SET NAMES {$this->connConfig['dbCharset']}");//设置连接字符集
        }
    }    
    
	/**
     * 返回当前链接的PDO对象
     * @return void
     */
    public function getPdo()
    {
    	return $this->pdo;
    }
    
	/**
     * 返回一个PDOStatement对象,可以执行PDOStatement的对象;
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return PDOStatement;
     */
    public function getPDOStatement($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
    	//验证参数
    	
		if(!is_bool($isProc)) $this->errorMsg('$isProc 参数必须为bool类型.');
		if(!is_string($cmdText)) $this->errorMsg('$cmdText 参数必须为string类型.');
		if(!is_array($commandParameters) && isset($commandParameters)) $this->errorMsg('$commandParameters 参数必须为array类型.');
    	$cmdText = $isProc ? $this->getProcText($cmdText,$commandParameters) : $cmdText;
	    $com = $this->pdo->prepare($cmdText);	  
	    $this->bindValue($com,$commandParameters);
	    $com->execute() or $this->errorMsg($com->errorInfo());
	    return $com;
    }
    
	/**
     * 返回一个数值表示此SqlCommand命令执行后影响的行数.注意不支持out类型参数的PROC
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @param bool $isProc 是否存储过程.
     * @return int 返回影响的行数.
     */
    public function executeNonQuery($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);
	    return $com->rowCount();
    }
    
	/**
     * 执行一个操作,并返回首行首列的内容.
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return mixed 并返回首行首列的内容.
     */
    public function executeAndReturn($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
	    $columnValue = 0;
    	//$this->pdo->beginTransaction();
    	try{
	    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);
			$columnValue = $com->fetchColumn(0);
	    	//$this->pdo->commit();
    	}
    	catch(Exception $ex)
    	{
    		//$this->pdo->rollBack();
    		throw new Exception($ex);
    	}
	    return $columnValue;
    }
    
/**
     * 返回结果集中每一行的数据,可以通过字段名和索引取值
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.@example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @param int $fetch_style=PDO::FETCH_BOTH
     * @return array
     */
    public function fetchBoth($cmdText, array $commandParameters=null, $isProc=FALSE )
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);
    	//验证参数$fetch_style
    	return $com->fetch(PDO::FETCH_BOTH);
    }
    
    /**
     * 返回结果集中每一行的数据,可以通过字段名取值
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.@example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @param int $fetch_style=PDO::FETCH_BOTH
     * @return array
     */
    public function fetchAssoc($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);
    	//验证参数$fetch_style
    	return $com->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 返回结果集中每一行的数据,可以通过索引取值
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.@example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @param int $fetch_style=PDO::FETCH_BOTH
     * @return array
     */
    public function fetchIndex($cmdText, array $commandParameters=null, $isProc=FALSE )
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);
    	//验证参数$fetch_style
    	return $com->fetch(PDO::FETCH_NUM);
    }
    
    /**
     * 返回结果集中每一行的数据对象,可以通过对象->字段名取值.
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return object
     */
    public function fetchObject($cmdText, array $commandParameters=null, $isProc=FALSE )
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);
    	//验证参数$fetch_style
    	return $com->fetchObject();
    }
    
    /**
     * 返回一个数组集,如果有多个select也可以返回.可以通过字段下标和索引下标搜索
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return array;
     */
    public function  fetchAllBoth($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
    	return $this->fetchAll($cmdText,PDO::FETCH_BOTH, $commandParameters, $isProc);    	
    }

    /**
     * 返回一个数组集,如果有多个select也可以返回.只能通过索引下标搜索
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return array;
     */
    public function  fetchAllIndex($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
    	return $this->fetchAll($cmdText,PDO::FETCH_NUM, $commandParameters, $isProc);    	
    }
      
    /**
     * 返回一个数组集,如果有多个select也可以返回.只能通过字段下标搜索
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return array;
     */
    public function  fetchAllAssoc($cmdText, array $commandParameters=null, $isProc=FALSE)
    {
    	return $this->fetchAll($cmdText,PDO::FETCH_ASSOC, $commandParameters, $isProc);    	
    }
    
    /**
     * 返回每一行某列的一个值;
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @param $index 列索引
     * @return mixed;
     */
    public function fetchColumn($cmdText, array $commandParameters=null, $isProc=FALSE, $index=0)
    {
		if(!is_int($index)) $this->errorMsg('索引参数必须为整数.');
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);		
		$res = null;
		try{
			$res = $com->fetchColumn($index);
		}catch(Exception $ex)
		{			
			$this->errorMsg($ex->getMessage());
		}
		return $res;		
    }
    
 	private function fetchAll($cmdText, $fetchMode = PDO::FETCH_BOTH, array $commandParameters=null, $isProc=FALSE)
    {
    	if(!is_int($fetchMode))$this->errorMsg('$fetchMode 参数必须为int类型.');
    	//fetchArr = array(PDO::FETCH_LAZY,PDO::FETCH_ASSOC,PDO::FETCH_NUM,PDO::FETCH_BOTH,PDO::FETCH_OBJ,
    	//PDO::FETCH_BOUND,PDO::FETCH_COLUMN,PDO::FETCH_CLASS,PDO::FETCH_INTO,PDO::FETCH_FUNC,PDO::FETCH_GROUP
    	//,PDO::FETCH_UNIQUE,PDO::FETCH_KEY_PAIR,PDO::FETCH_CLASSTYPE,PDO::FETCH_SERIALIZE,PDO::FETCH_PROPS_LATE,PDO::FETCH_NAMED);    	
    	//if($fetchArr)
    	
    	$com = $this->getPDOStatement($cmdText, $commandParameters, $isProc);    	
    	$com -> setFetchMode($fetchMode);
    	$resArr = array();
    	do {
		    $rowset = $com->fetchAll();
		    if (!$rowset)$rowset = array();
		    
		    array_push($resArr,$rowset);
		} while ($com->nextRowset());
    	return count($resArr)==1?$resArr[0]:$resArr;
    }
    
	private function bindValue(PDOStatement $com, array $commandParameters=null)
    {    
    	if(!isset($commandParameters))return;
	    foreach ($commandParameters as $k=>$v)
	    {
	    	if(!is_int($v[2])&&isset($v[2])) $this->errorMsg('绑定的参数类型必须为整型.');
	    	//效验参数类型    	
			//bindParam
	    	//if(isset($v[3]))
	    	//	$com ->bindValue($v[0],$v[1],$v[2],$v[3]);
	    	//else 		
	    	// mixed $parameter , mixed $value [, int $data_type ]
	    	$parameter = $v[0];
	    	$value = $v[1];
	    	$dataType = isset($v[2])?$v[2]:PDO::PARAM_STR; 
	    	$com ->bindValue($parameter,$value,$dataType);
	    }    	
    }
    
	private function getProcText($cmdText, array $commandParameters=null)
    {
    	if(strpos($cmdText,'(')!== false) return $cmdText;
    	$params = '';
    	if(isset($commandParameters)){
	    	foreach($commandParameters as $k => $v)
	    	{
	   			$params .= (is_int($v[0])?'?':$v[0]).',';
	    	}
    	}
    	if(strlen($params)>0)$params = substr($params,0,strlen($params)-1);
    	return "call {$cmdText}({$params})";
    }
    
	/**
     * 取mysql版本
     * @return string 
     */
    public function getVersion()
    {
        $mysqlVersion = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
        return doubleval($mysqlVersion);
    }
	
    /**
     * 关闭数据库对象
     * @return void
     */
    public function closeConnect()
    {//var_dump('XXXXXXXXXXX关闭了连接<br/>');
        if ($this->pdo != null)
        {
            $this->pdo = null;
        }
    }
    
	/**
  	 * 执行SQL出错时，调用此函数
  	 * @param string $msg
  	 */
  	private function errorMsg($msg)
    {
        die(is_string($msg)?$msg:$msg[2]);
    }
    
	function __destruct ()
	{		
        $this->closeConnect();
	}
}

?>